/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder;

import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * SqlSource对象的构建器
 *
 * @author Clinton Begin
 */
public class SqlSourceBuilder extends BaseBuilder {

    private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

    public SqlSourceBuilder(Configuration configuration) {
        super(configuration);
    }

    /**
     * 解析成SQL的PreStatement
     */
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        // 解析 【#{】和【}】直接的内容
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

    private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

        /**
         * 参数映射表
         */
        private List<ParameterMapping> parameterMappings = new ArrayList<>();
        /**
         * 参数类型
         */
        private Class<?> parameterType;
        /**
         * 对象元数据
         */
        private MetaObject metaParameters;

        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
            super(configuration);
            this.parameterType = parameterType;
            // 生成对象元数据
            this.metaParameters = configuration.newMetaObject(additionalParameters);
        }

        public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
        }

        @Override
        public String handleToken(String content) {
            // 处理占位符标签
            parameterMappings.add(buildParameterMapping(content));
            return "?";
        }

        /**
         * 基于构建参数映射表
         *
         * @param content 表达式
         */
        private ParameterMapping buildParameterMapping(String content) {
            // 获取表达式对应的参数集合
            Map<String, String> propertiesMap = parseParameterMapping(content);
            // 获取表达式对应的参数名称
            String property = propertiesMap.get("property");
            Class<?> propertyType;
            if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
                // 拥有java字段获取入参类型
                propertyType = metaParameters.getGetterType(property);
            } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
                // 通过类型转换器获取入参类型
                propertyType = parameterType;
            } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
                // 通过jdbc获取入参类型
                propertyType = java.sql.ResultSet.class;
            } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
                // 没有定义属性，但是解析出来了参数，设置为Object入参类型
                propertyType = Object.class;
            } else {
                // 通过反射工厂获取入参类型的元数据
                MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());

                if (metaClass.hasGetter(property)) {
                    // 通过字段的Get方法获取类型
                    propertyType = metaClass.getGetterType(property);
                } else {
                    // 定义为Object类型
                    propertyType = Object.class;
                }
            }

            // 构建入参映射
            ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);

            Class<?> javaType = propertyType;
            String typeHandlerAlias = null;
            for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                if ("javaType".equals(name)) {
                    // 解析表达式内的java类型
                    javaType = resolveClass(value);
                    // 重置入参类型
                    builder.javaType(javaType);
                } else if ("jdbcType".equals(name)) {
                    // 重置表达式内的jdbc类型
                    builder.jdbcType(resolveJdbcType(value));
                } else if ("mode".equals(name)) {
                    // 重置表达式内的参数模式
                    builder.mode(resolveParameterMode(value));
                } else if ("numericScale".equals(name)) {
                    // 重置表达式内指定的数字精度
                    builder.numericScale(Integer.valueOf(value));
                } else if ("resultMap".equals(name)) {
                    // 重置表达式内指定对应的resultMap
                    builder.resultMapId(value);
                } else if ("typeHandler".equals(name)) {
                    /*重置类型处理器*/
                    typeHandlerAlias = value;
                } else if ("jdbcTypeName".equals(name)) {
                    /*重置jdbc列名称*/
                    builder.jdbcTypeName(value);
                } else if ("property".equals(name)) {
                    // 对于字段名称不进行任何处理
                    // Do Nothing
                } else if ("expression".equals(name)) {
                    // 不支持表达式参数
                    throw new BuilderException("Expression based parameters are not supported yet");
                } else {
                    throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
                }
            }
            if (typeHandlerAlias != null) {
                // 重置指定的类型解析器
                builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
            }
            return builder.build();
        }

        /**
         * 解析指定的表达式，并获取其中的表达式内容以及jdbc类型，参数定义等内容
         *
         * @param content 表达式
         * @return 表达式定义的数据
         */
        private Map<String, String> parseParameterMapping(String content) {
            try {
                // 生成表达式对应的参数集合
                return new ParameterExpression(content);
            } catch (BuilderException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
            }
        }
    }

}
